package xyz.scootaloo.kami.server.service

import io.vertx.ext.web.FileUpload
import xyz.scootaloo.kami.server.controller.dto.StatusCode
import xyz.scootaloo.kami.server.model.ActualUploadFileInfo
import xyz.scootaloo.kami.server.model.UploadFileResponse
import xyz.scootaloo.kami.server.standard.FileNotInTaskException
import xyz.scootaloo.kami.server.standard.UploadTaskNotExistsException
import xyz.scootaloo.kami.server.service.CrontabService.DatabaseCrontabHandler
import xyz.scootaloo.kami.server.service.impl.InternalUploadServiceImpl

/**
 * 名词解释, 有两个比较常见, 容易混淆的概念:
 *
 * - `Chunk`: 块, 文件分块后每一块的实体称为`Chunk`
 * - `Chunked`: 分块, 将一个整体的大文件拆分为多个小文件, 这个拆分的动作称为`Chunked`
 *
 * @author flutterdash@qq.com
 * @since 2022/2/24 23:31
 */
interface UploadService : DatabaseCrontabHandler {

    companion object {
        operator fun invoke(): UploadService {
            return InternalUploadServiceImpl
        }
    }

    data class UploadFileChunk(
        val taskId: String,
        val filename: String,
        val chunkedSize: Long,
        val chunkNum: Int,
        val uploadFile: FileUpload
    )

    /**
     * 创建上传任务
     *
     * 这里会根据要上传的文件列表, 生成一个任务信息,
     */
    suspend fun createUploadTask(
        uploader: Int, relativePath: String, files: List<ActualUploadFileInfo>
    ): UploadFileResponse

    /**
     * 执行上块上传任务
     *
     * 这个方法会校验一个文件块是否合法, 检查内容包括以下几条:
     * - 文件块属于一个文件, 而一个文件又属于一个上传任务, 需要确定这个文件是否存在于上传任务中
     * - 一个文件的分块上传有唯一的分块依据, 而且这个分块依据在服务器中有备份, 此次上传的文件分块依据是否和服务器中的副本一致
     * - 按照文件的分块依据对文件进行分块, 每一个文件块都有一个编号, 检查上传文件的文件编号是否在预期范围内
     * - 分块依据限制了每一个文件块的大小, 但是每一个文件块可能因为位置不同而大小不同,
     * 根据文件块的编号重新对文件大小进行计算, 检查是否一致
     *
     * 假如上述的检查项都通过了, 则将文件块移动到目标位置, 然后更新进度;
     * 当进度更新完成后, 此方法返回, 并返回一个状态码, 标识文件块的上传状态
     */
    @Throws(UploadTaskNotExistsException::class, FileNotInTaskException::class)
    suspend fun handleUploadFileChunk(chunkInfo: UploadFileChunk): StatusCode

    suspend fun findSubTaskInfo(taskId: String, filename: String): ActualUploadFileInfo

    suspend fun findExistsTaskInfo(taskId: String): UploadFileResponse
}